#include "OpenGlGridDisplay.h"

#include <unistd.h>

#include <algorithm>
using std::max;

#include <GL/glut.h>
#include <GL/freeglut.h>

#include "../../../base.h"
#include "../../../graphics/GlutCallbacks.h"
#include "../../../qt/Logger.h"

#include "../grid/SimpleGrid.h"

#include "../World.h"

#include "Palette.h"

#define WINDOW_WIDTH	640
#define WINDOW_HEIGHT	640

#define CAMERA_START_X		0.0
#define CAMERA_START_Y		0.0
#define CAMERA_START_Z		800.0
#define CAMERA_AOV		60.0
#define CAMERA_CLIP_NEAR	1.0
#define CAMERA_CLIP_FAR		10000.0
#define CAMERA_Z_MINIMUM	5.0

#define CAMERA_DELTA_X		3.0
#define CAMERA_DELTA_Y		3.0
#define CAMERA_DELTA_Z		50.0

#define TILE_SIZE		10.0
#define LINE_THICKNESS		1.2
#define LINE_Z			0.5

#define GRID_HISTORY_SIZE	200


/*
  	OpenGlGridDisplay
*/

/*	public	*/

OpenGlGridDisplay::OpenGlGridDisplay(
	const World& world) :
	_world(world),
	_paused(true),
	_oneOff(false),
	_cameraX(CAMERA_START_X),
	_cameraY(CAMERA_START_Y), 
	_cameraZ(CAMERA_START_Z),
	_gridIndex(-1),
	_lastGridIndex(-1) {

	/* Initialise Glut */
	int    argc = 0;
	char** argv = 0;
	glutInit(&argc, argv);
	glutInitWindowSize(WINDOW_WIDTH, WINDOW_HEIGHT);
	glutInitDisplayMode(GLUT_RGB|GLUT_DOUBLE|GLUT_DEPTH);

	/* create window */
	int mainHandle = glutCreateWindow("Cells");
	glutSetWindow(mainHandle);

	/* register the callbacks */
	GlutCallbacks* callbacks = GlutCallbacks::getInstance();
	callbacks->registerDisplayListener(this);
	callbacks->registerIdleListener(this);
	callbacks->registerKeyboardListener(this);
	callbacks->registerMouseListener(this);
	callbacks->registerReshapeListener(this);
	callbacks->registerSpecialListener(this);

//	glClearColor(0.0f, 0.0f, 0.0f, 1.0);
	glClearColor(0.1f, 0.2f, 0.3f, 1.0);
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_COLOR_MATERIAL);

	/* GL lighting */
	glEnable(GL_LIGHTING);
	float ambientColor[]	= { 0.3f, 0.3f, 0.3f, 0.0f };
	float diffuseColor[]	= { 1.0f, 1.0f, 1.0f, 0.0f };		
	glLightfv(GL_LIGHT0, GL_AMBIENT, ambientColor);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseColor);
//
//	float specularColor[]	= { 0.0f, 0.0f, 0.0f, 0.0f };		
//	float position[]	= { 100.0f, 100.0f, 400.0f, 1.0f };		
//	glLightfv(GL_LIGHT0, GL_SPECULAR, specularColor);
//	glLightfv(GL_LIGHT0, GL_POSITION, position);
	glEnable(GL_LIGHT0);

}

OpenGlGridDisplay::~OpenGlGridDisplay() {
	deleteall<Grid>(_gridHistory);
}

void OpenGlGridDisplay::updateGridDisplay() {
//	Logger::debug(">>> OpenGlGridDisplay::updateGridDisplay()");

	/* indices */
	assert(_lastGridIndex == _gridIndex);
	++_lastGridIndex;
	++_gridIndex;

	/* save grid to history */
	doSaveGrid();

	glutMainLoopEvent();

	glutPostRedisplay();

//	/* one off */
//	if (_oneOff) {
//		_oneOff = false;
//		_paused = true;
//	} else if (_oneOffBack) {
//		_paused = true;
//	}

	while(_paused || _gridIndex != _lastGridIndex) {
//		Logger::debug("\twhile(_paused || _gridIndex != _lastGridIndex");
		usleep(500);
		glutMainLoopEvent();

//		if (_oneOffBack) {
//			_oneOffBack = false;
//			glutPostRedisplay();
//		}

		if (!_paused && _gridIndex != _lastGridIndex) {
			++_gridIndex;
			glutPostRedisplay();
		}

//		/* one off */
//		if (_oneOff == true) {
//			if (_gridIndex == _lastGridIndex) {
//				break;
//			} else {
//				_oneOff = false;
//				_paused = true;
//			}
//		}
	}

	if (_oneOff == true) {
		_oneOff = false;
		_paused = true;
	}

//	Logger::debug("<<< OpenGlGridDisplay::updateGridDisplay()");
}

void OpenGlGridDisplay::callbackIdle() {
//	glutPostRedisplay();
}

void OpenGlGridDisplay::callbackKeyboard(unsigned char key, int UNUSED(x), int UNUSED(y)) {
	bool changed = false;

	switch(key) {
		case ' ':		/* space */
			_paused = !_paused;
			break;
		case '.':		/* dot */
			if (_paused) {
				_paused = false;
				_oneOff = true;
			}

			if (_gridIndex != _lastGridIndex) {
				++_gridIndex;
				changed = true;
				_paused = true;
				_oneOff = false;
			}

			break;
		case 8:			/* backspace */
//			_oneOffBack = true;
			_gridIndex = max(_gridIndex - 1, 0);
			_paused = true;
			changed = true;
			break;
		case GLUT_KEY_UP:	/* up - go up */
//			std::cout << "*** key up\n";
			_cameraY += CAMERA_DELTA_Y;
			changed = true;
			break;

		case GLUT_KEY_DOWN:	/* down - go down */
			_cameraY -= CAMERA_DELTA_Y;
			changed = true;
			break;

		case GLUT_KEY_LEFT:	/* left - go left */
			_cameraX -= CAMERA_DELTA_X;
			changed = true;
			break;

		case GLUT_KEY_RIGHT:	/* right - go right */
			_cameraX += CAMERA_DELTA_X;
			changed = true;
			break;

		case GLUT_KEY_PAGE_DOWN:	/* page down - zoom out */
			_cameraZ += CAMERA_DELTA_Z;
			changed = true;
			break;

		case GLUT_KEY_PAGE_UP:		/* page up - zoom in */
			_cameraZ -= CAMERA_DELTA_Z;

			if (_cameraZ < CAMERA_Z_MINIMUM) {
				_cameraZ = CAMERA_Z_MINIMUM;
			}

			changed = true;
			break;
//		case 27:
//		case 81:
		case 27:
		case 'q':
		case 'Q':		/* escape, Q, q - quit */
			exit(0);
			break;

	}

	if (changed) {
		glutPostRedisplay();
	}
}

/* do nothing with the mouse for now */
void OpenGlGridDisplay::callbackMouse(int UNUSED(button), int UNUSED(state), int UNUSED(x), int UNUSED(y)) {}

void OpenGlGridDisplay::callbackDisplay() {
//	Logger::debug("=== OpenGlGridDisplay::callbackDisplay()");

	/* clear buffers */
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	/* projection matrix */
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(
		CAMERA_AOV,
		(double)glutGet(GLUT_WINDOW_WIDTH)/(double)glutGet(GLUT_WINDOW_HEIGHT),
		CAMERA_CLIP_NEAR,
		CAMERA_CLIP_FAR);
	gluLookAt(_cameraX, _cameraY, _cameraZ, _cameraX, _cameraY, 0.0, 0.0, 1.0, 0.0);

	/* modelview matrix */
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	/* render the grid */

//	const Grid& grid = getGrid();
	Grid const& grid = *(_gridHistory[_gridIndex]);

	/* grid dimensions */
	int gridWidth  = grid.getWidth();
	int gridHeight = grid.getHeight();

	/* grid's bottom left position */
	float originX = - gridWidth  * TILE_SIZE / 2.0;
	float originY = - gridHeight * TILE_SIZE / 2.0;

	/* draw tiles */
	glBegin(GL_QUADS);
	Palette const& palette = getPalette();
	for(int i = 0; i < gridWidth; ++i) {
		for(int j = 0; j < gridHeight; ++j) {
			/* color */
			Color const& color = palette.getColor(grid.getTile(i, j));
			glColor4f(color.red(), color.green(), color.blue(), 1.0f);

			/* bottom left position of the tile */
			float tileOriginX = originX + i * TILE_SIZE;
			float tileOriginY = originY + j * TILE_SIZE;

			glNormal3f(0.0f, 0.0f, 1.0f);
			glVertex3f(tileOriginX,             tileOriginY,             0.0);
			glVertex3f(tileOriginX,             tileOriginY + TILE_SIZE, 0.0);
			glVertex3f(tileOriginX + TILE_SIZE, tileOriginY + TILE_SIZE, 0.0);
			glVertex3f(tileOriginX + TILE_SIZE, tileOriginY,             0.0);
		}
	}

	glEnd();

	/* draw lines */
//	glBegin(GL_QUADS);
//	/* color */
//	glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
//
//	/* vertical */
//	float y1 = originY;
//	float y2 = originY + gridHeight * TILE_SIZE;
//	for(int i = 0; i < gridWidth - 1; ++i) {
//		float x1 = originX + i * TILE_SIZE - (LINE_THICKNESS / 2.0);
//		float x2 = originX + i * TILE_SIZE + (LINE_THICKNESS / 2.0);
//
//		glNormal3f(0.0f, 0.0f, 1.0f);
//		glVertex3f(x1, y1, LINE_Z);
//		glVertex3f(x1, y2, LINE_Z);
//		glVertex3f(x2, y2, LINE_Z);
//		glVertex3f(x2, y1, LINE_Z);
//	}
//
//	/* horizontal */
//	float x1 = originX;
//	float x2 = originX + gridWidth * TILE_SIZE;
//	for(int j = 0; j < gridWidth - 1; ++j) {
//		float y1 = originY + j * TILE_SIZE - (LINE_THICKNESS / 2.0);
//		float y2 = originY + j * TILE_SIZE + (LINE_THICKNESS / 2.0);
//
//		glNormal3f(0.0f, 0.0f, 1.0f);
//		glVertex3f(x1, y1, LINE_Z);
//		glVertex3f(x1, y2, LINE_Z);
//		glVertex3f(x2, y2, LINE_Z);
//		glVertex3f(x2, y1, LINE_Z);
//	}
//
//	glEnd();

	glutSwapBuffers();
}

void OpenGlGridDisplay::callbackReshape(int width, int height) {
	glViewport(0, 0, width, height);
}

void OpenGlGridDisplay::callbackSpecial(int key, int x, int y) {
	callbackKeyboard(key, x, y);
}

void OpenGlGridDisplay::doSaveGrid() {
	if (_gridHistory.size() == GRID_HISTORY_SIZE) {
		delete _gridHistory.front();
		_gridHistory.pop_front();
		--_lastGridIndex;
		--_gridIndex;
	}

	if (_lastGridIndex != 0) {
		/* don't update when fetching first grid ever */
		updateWorld();
	}

	_gridHistory.push_back(_world.getSimpleGridCopy());

	assert(_gridHistory.size() == (uint)(_gridIndex + 1));
}

